﻿using System;
using System.Text;
using System.Globalization;

namespace GSMMODEM
{
    #region 接收短信定义
    /// <summary>
    /// 接收短信定义
    /// </summary>
    public class SmsInfo
    {
        public string Center = string.Empty;       //服务中心
        public string Number = string.Empty;       //发送方手机号
        public DateTime TimeStamp = new DateTime();//短信时间
        public string Message = string.Empty;      //短信内容
    }
    #endregion

    #region PDU解析控制
    /// <summary>
    /// PDU 编码读取操作类
    /// </summary>
    public class PDUReader
    {
        public string PDUString { private set; get; }
        public int Offset { set; get; }
        /// <summary>
        /// PDU 读取器
        /// </summary>
        /// <param name="source">PDU 编码字符串</param>
        /// <param name="offset">偏移量</param>
        public PDUReader(string source, int offset)
        {
            PDUString = source;
            Offset = offset;
        }
        /// <summary>
        /// 可否偏移指针
        /// </summary>
        /// <returns></returns>
        public bool HasNext()
        {
            return Offset < PDUString.Length;
        }
        /// <summary>
        /// 下一个 Unicode 字符
        /// </summary>
        /// <returns>Unicode 字符</returns>
        public char NextChar()
        {
            return PDUString[Offset++];
        }
        /// <summary>
        /// 下一组 Unicode 字符
        /// </summary>
        /// <param name="length">读取长度</param>
        /// <returns>Unicode 字符串</returns>
        public string NextString(int length)
        {
            StringBuilder retValue = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                if (!HasNext())
                {
                    break;
                }
                retValue.Append(NextChar());
            }
            return retValue.ToString();
        }
    }

    #endregion

    #region PDU编码

    /// <summary>
    /// PDU 编码
    /// </summary>
    public class PDUEncoder
    {
        #region 字段属性

        /// <summary>
        /// 协议数据单元类型(1个8位组)
        /// </summary>
        public struct ProtocolDataUnitType
        {
            public const string TP_VP = "11";
        }

        /// <summary>
        /// 所有成功的短信发送参考数目（0..255）
        /// (1个8位组)
        /// </summary>
        public struct MessageReference
        {
            public const string TP_MR = "00";
        }
        /// <summary>
        /// 参数显示消息中心以何种方式处理消息内容
        /// （比如FAX,Voice）(1个8位组)
        /// </summary>
        public struct ProtocolIdentifer
        {
            public const string TP_PID = "00";
        }

        /// <summary>
        /// 参数显示用户数据编码方案(1个8位组)
        /// </summary>
        public struct EncodingCharSet
        {
            public const string Bit7 = "00";
            public const string Bit8 = "18";
            public const string Usc2 = "08";

        }
        /// <summary>
        /// 短消息有效期(0,1,7个8位组)
        /// </summary>
        public struct ValidityPeriod
        {
            /// <summary>
            /// 00 to 8F(0 to 143)          (VP+1)*5 分钟  
            /// </summary>
            public const string Min5 = "00";
            /// <summary>
            /// 90 to A7(144 to 167)          12小时+(VP-143)*30分钟 
            /// </summary>
            public const string Hour12 = "90";
            /// <summary>
            /// A8 to C4(168 to 196)          (VP-166)*1天 
            /// </summary>
            public const string Day1 = "C4";//暂时固定有效期
            /// <summary>
            /// C5 to FF(197 to 255)           (VP-192)*1 周
            /// </summary>
            public const string Week1 = "C5";

        }
        #endregion
        #region
        /// <summary>
        /// 消息服务中心编码
        /// (1-12个8位组)
        /// </summary>
        /// <param name="centerNumber">短信中心号码</param>
        /// <returns></returns>
        public string EncodeCenterNumber(string centerNumber)
        {
            if (!String.IsNullOrEmpty(centerNumber))
            {
                centerNumber = centerNumber.TrimStart('+');
                //如果不是以+86 开头 +86
                if (!centerNumber.StartsWith("86"))
                {
                    centerNumber = centerNumber.Insert(0, "86");
                }
                centerNumber = ParityChange(centerNumber);
                centerNumber = centerNumber.Insert(0, "91"); //添加 SMSC地址格式
                string length = (centerNumber.Length / 2).ToString("X2");
                centerNumber = centerNumber.Insert(0, length);//添加 号码长度
            }
            return centerNumber;
        }
        /// <summary>
        /// 接收方地址编码
        /// </summary>
        /// <param name="phoneNumber">手机号码(2-12个8位组)</param>
        /// <returns></returns>
        public string EncodeDestinationNumber(string phoneNumber)
        {
            if (!String.IsNullOrEmpty(phoneNumber))
            {
                phoneNumber = phoneNumber.TrimStart('+');
                if (phoneNumber.StartsWith("86"))
                {
                    //不要包含 86
                    phoneNumber = phoneNumber.TrimStart('8','6');
                }
                //目标地址数字个数 共13个十进制数(不包括91和‘F’)
                string length = (phoneNumber.Length).ToString("X2");
                phoneNumber = ParityChange(phoneNumber);
                //目标地址格式(TON/NPI) 用国际格式号码(在前面加‘+’) A1 ?
                phoneNumber = String.Concat(length, "A1", phoneNumber);
            }
            return phoneNumber;
        }
        /// <summary>
        /// 用于信息内容的编码
        /// 用户数据(0-140个8位组)
        /// </summary>
        /// <param name="content">信息内容</param>
        /// <param name="encodingCharSet">编码使用的方案</param>
        /// <returns>PDU</returns>
        public string EncodeUserMessage(string content, string encodingCharSet)
        {
            string userData = String.Empty,userDataLenghth = "00";
            switch (encodingCharSet)
            {
                case "08":
                    //Unicode
                    userData = Ucs2Encoding(content);
                    userDataLenghth = (userData.Length / 2).ToString("X2");
                    break;
                case "18":
                    //8Bit
                    userData = Bit8Encoding(content);
                    userDataLenghth = (userData.Length / 2).ToString("X2");
                    break;
                case "00":
                    //7Bit
                    userData = Bit7Encoding(content);
                    userDataLenghth = content.Length.ToString("X2");
                    break;
                default:
                    //Default
                    userData = Bit7Encoding(content);
                    break;
            }
            
            return userDataLenghth + userData;

        }
        #endregion

        public static string Encoding(string center, string phone, string content, out int length)
        {
            PDUEncoder pduEncoding = new PDUEncoder();
            return pduEncoding.EncodingSms(center, phone, content, out length);
        }
        public string EncodingSms(string center, string phone, string content, out int length)
        {
            string centerNumber = EncodeCenterNumber(center);
            string phoneNumber = EncodeDestinationNumber(phone);
            const string encodingCharSet = EncodingCharSet.Usc2;
            string userMessage = EncodeUserMessage(content, encodingCharSet);

            string retValue = string.Concat(
                centerNumber,
                ProtocolDataUnitType.TP_VP,
                MessageReference.TP_MR,
                phoneNumber,
                ProtocolIdentifer.TP_PID,
                encodingCharSet,
                ValidityPeriod.Day1,
                userMessage
                );

            string s1 = retValue.Substring(0, 2);
            int i1 = Convert.ToInt32(s1, 16);
            i1 = retValue.Length - i1 * 2 - 2;
            length = i1 / 2;  //计算长度
            return retValue;
        }
        /// <summary>
        /// 手机号奇偶互换并补F
        /// </summary>
        /// <param name="value">接收短信手机号码</param>
        /// <returns></returns>
        private static string ParityChange(string value)
        {
            string result = string.Empty;
            int length = value.Length;
            for (int i = 1; i < length; i += 2) //奇偶互换
            {
                result += value[i];
                result += value[i - 1];
            }

            if (length % 2 != 0) //不是偶数则加上F，与最后一位互换
            {
                result += 'F';
                result += value[length - 1];
            }
            return result;
        }
        /// <summary>
        /// 将发送短信内容进行编码
        /// 采用Big-Endian 字节顺序的 Unicode 格式编码，将高低位互换
        /// 将转换后的短信内容存进字节数组
        /// 去掉在进行Unicode格式编码中，两个字节中的"-",例如：00-21，变成0021
        /// 将整条短信内容的长度除2，保留两位16进制数
        /// </summary>
        /// <param name="text">消息内容</param>
        /// <returns>返回消息编码后的长度</returns>
        private static string Ucs2Encoding(string text)
        {
            //对短信内容进行编码 
            string result = string.Empty;
            Encoding encoding = System.Text.Encoding.BigEndianUnicode;

            byte[] bytes = encoding.GetBytes(text);

            for (int i = 0; i < bytes.Length; i++)
            {
                result += BitConverter.ToString(bytes, i, 1);
            }
            return result;
        }
 
        /// <summary>
        /// Bit 7 编码
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private static string Bit7Encoding(string text)
        {
            //存储中间字符串 二进制串
            string userData = "", temp = "";
            //
            Encoding encodingAsscii = System.Text.Encoding.ASCII;
            //
            byte[] bytes = encodingAsscii.GetBytes(text);
            //高低交换 二进制串
            for (int i = text.Length; i > 0; i--)
            {
                temp = Convert.ToString(bytes[i - 1], 2);
                //不够7位，补齐
                while (temp.Length < 7)
                {
                    temp = temp.Insert(0, "0");
                }
                userData += temp;
            }
            return Bit8Encoding(userData);
        }
        /// <summary>
        /// Bit 8 编码
        /// 每8位取位一个字符 即完成编码
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private static string Bit8Encoding(string text)
        {
            string result = String.Empty;
            //每8位取位一个字符 即完成编码
            for (int i = text.Length; i > 0; i -= 8)
            {
                result += Convert.ToInt32(i > 8 ? text.Substring(i - 8, 8) : text.Substring(0, i), 2).ToString("X2");
            }
            return result;
        }
    }
        
    #endregion

    #region PDU解码
    public class PDUDecoder
    {
        private readonly string _pduString = String.Empty;
        /// <summary>
        /// PDU 解码 
        /// </summary>
        /// <param name="PDUString">PDU 编码字符串</param>
        /// <returns>短信息</returns>
        public static SmsInfo Decoding(string PDUString)
        {
            PDUDecoder pduDecoder = new PDUDecoder(PDUString);
            return pduDecoder.ToSmsInfo();
        }
        /// <summary>
        /// PDU解码
        /// </summary>
        /// <param name="source"></param>
        public PDUDecoder(string source)
        {
            _pduString = source;
        }
        /// <summary>
        /// 转换到SMSInfo
        /// </summary>
        /// <returns>短信息</returns>
        public SmsInfo ToSmsInfo()
        {
            SmsInfo smsInfo = new SmsInfo();

            PDUReader pduReader = new PDUReader(_pduString, 0);
            string centerNumber = DecodeNumber(ParseCenterNumber(pduReader));
            string firstOctet = ParseFirstOctet(pduReader);
            string senderNumber = DecodeNumber(ParseSenderNumber(pduReader));
            string protocol = ParseProtocol(pduReader);
            string characterEncoding = ParseCharsetEncoding(pduReader);
            DateTime timeStamp = DecodeTimestamp(ParseCenterTime(pduReader));
            string message = DecodeMessage(ParseUserData(pduReader), characterEncoding);

            smsInfo.Center = centerNumber;
            smsInfo.Number = senderNumber;
            smsInfo.TimeStamp = timeStamp;
            smsInfo.Message = message;

            return smsInfo;
        }
        private static string DecodeNumber(string number)
        {
            StringBuilder sb = new StringBuilder();
            PDUReader reader = new PDUReader(number, 0);
            string str = reader.NextString(2);
            if (str == "91")
            {
                sb.Append("+");
            }
            while (reader.HasNext())
            {
                char one = reader.NextChar();
                char two = reader.NextChar();
                sb.Append(two);
                sb.Append(one);
            }
            if (sb[sb.Length - 1] == 'F' || sb[sb.Length - 1] == 'f')
            {
                sb.Remove(sb.Length - 1, 1);
            }
            return sb.ToString();
        }
        private static DateTime DecodeTimestamp(string timestampString)
        {
            DateTime timeStamp = new DateTime();
            //分析接收时间    
            timestampString = string.Format("{0}{1}/{2}{3}/{4}{5} {6}{7}:{8}{9}:{10}{11}",
                                                   timestampString[1], timestampString[0],
                                                   timestampString[3], timestampString[2],
                                                   timestampString[5], timestampString[4],
                                                   timestampString[7], timestampString[6],
                                                   timestampString[9], timestampString[8],
                                                   timestampString[11], timestampString[10]
                );
            try
            {
                timeStamp = Convert.ToDateTime(timestampString);
            }
            catch { ; }
            return timeStamp;
        }
        private string DecodeMessage(string userDate, string characterEncoding)
        {
            string message = String.Empty;
            if (!String.IsNullOrEmpty(userDate))
            {
                int code = Int32.Parse(characterEncoding, NumberStyles.HexNumber);
                switch (code)
                {
                    case 0: //bit7    
                        message = Bit7Decoding(userDate);
                        break;
                    case 8: //Uncode
                        byte[] bytes1 = new byte[userDate.Length / 2];
                        for (int i = 0, j = 0; j < bytes1.Length; i += 2, j++)
                        {
                            bytes1[j] = Byte.Parse(userDate.Substring(i, 2), NumberStyles.HexNumber);
                        }
                        message = Encoding.BigEndianUnicode.GetString(bytes1);
                        break;
                    default:
                        byte[] bytes2 = new byte[userDate.Length / 2];
                        for (int i = 0, j = 0; j < bytes2.Length; i += 2, j++)
                        {
                            bytes2[j] = Byte.Parse(userDate.Substring(i, 2), NumberStyles.HexNumber);
                        }
                        message = Encoding.ASCII.GetString(bytes2);
                        break;
                }
            }
            return message;
        }
        #region 编码转换

        /// <summary>
        /// 解释PDU编译的内容
        /// </summary>
        /// <param name="textEncoded">PDU编译的内容</param>
        /// <returns>翻译后的内容</returns>
        public string Bit7Decoding(string textEncoded)
        {
            byte[] src = HexStringToBytes(textEncoded);
            if (src.Length == 0)
            {
                return string.Empty;
            }
            int srcLength = src.Length;
            int dstLength = srcLength * 8 / 7;
            var dst = new byte[dstLength];
            int a, b;
            for (a = 0, b = 0; b < srcLength; a++, b++)
            {
                int k = a % 8;
                if (a > 0)
                {
                    dst[a] = (byte)(((src[b] << k) & 0x7f) | (src[b - 1] >> 8 - k));
                }
                else
                {
                    dst[a] = (byte)(src[b] & 0x7f);
                }
                if (k == 7 && a > 0)
                {
                    dst[++a] = (byte)(src[b] & 0x7f);
                }
            }
            return Encoding.ASCII.GetString(dst);
        }
        /// <summary>
        /// PDU7bit的解码
        /// </summary>
        /// <param name="userData">数据部分PDU字符串</param>
        /// <returns></returns>
        private string Pdu7BitDecoder(string userData)
        {
            string result = string.Empty;
            byte[] b = new byte[100];
            string temp = string.Empty;
            for (int i = 0; i < userData.Length; i += 2)
            {
                b[i / 2] = (byte)Convert.ToByte((userData[i].ToString() + userData[i + 1].ToString()), 16);
            }

            int j = 0;            //while计数
            int tmp = 1;            //temp中二进制字符字符个数
            while (j < userData.Length / 2 - 1)
            {
                string s = string.Empty;

                s = Convert.ToString(b[j], 2);

                while (s.Length < 8)            //s补满8位 byte转化来的 有的不足8位，直接解码将导致错误
                {
                    s = "0" + s;
                }

                result += (char)Convert.ToInt32(s.Substring(tmp) + temp, 2);        //加入一个字符 结果集 temp 上一位组剩余

                temp = s.Substring(0, tmp);             //前一位组多的部分

                if (tmp > 6)                            //多余的部分满7位，加入一个字符
                {
                    result += (char)Convert.ToInt32(temp, 2);
                    temp = string.Empty;
                    tmp = 0;
                }

                tmp++;
                j++;

                if (j == userData.Length / 2 - 1)           //最后一个字符
                {
                    result += (char)Convert.ToInt32(Convert.ToString(b[j], 2) + temp, 2);
                }
            }
            return result;
        }
        /// <summary>
        /// 十六进制的字符串转换为byte数组
        /// </summary>
        /// <param name="hexString">转换内容</param>
        /// <returns>翻译后的串</returns>
        private static byte[] HexStringToBytes(string hexString)
        {
            int hexStringLength = hexString.Length;
            if (hexStringLength < 2 || hexStringLength % 2 != 0)
            {
                return new byte[0];
            }
            var data = new byte[hexStringLength / 2];
            for (int i = 0, j = 0; i < hexStringLength; i += 2, j++)
            {
                data[j] = Byte.Parse(hexString.Substring(i, 2), NumberStyles.HexNumber);
            }
            return data;
        }

        #endregion

        /// <summary>
        /// 解析短信中心号码
        /// </summary>
        /// <param name="pduReader">PDUReader 读取器</param>
        /// <returns>短信中心号码(编码)</returns>
        private static string ParseCenterNumber(PDUReader pduReader)
        {
            string str = pduReader.NextString(2);
            //取回地址信息长度(str * 2 = length)
            int length = Int32.Parse(str, NumberStyles.HexNumber) * 2;
            //偏移 2 //读取号码
            pduReader.Offset = 2;
            //短信中心号码 *编码
            return pduReader.NextString(length);
        }
        /// <summary>
        /// 解析 FirstOctet
        /// </summary>
        /// <param name="pduReader"></param>
        /// <returns></returns>
        private static string ParseFirstOctet(PDUReader pduReader)
        {
            return pduReader.NextString(2);
        }
        /// <summary>
        /// 解析发送方号码
        /// </summary>
        /// <param name="pduReader"></param>
        /// <returns></returns>
        private static string ParseSenderNumber(PDUReader pduReader)
        {
            string str = pduReader.NextString(2);
            if (str == "FF" || str == "00")
            {
                str = pduReader.NextString(2);
            }
            int length = Int32.Parse(str, NumberStyles.HexNumber);
            if (length % 2 != 0)
            {
                //如果长度不是偶数位 添加 'F' 补齐
                length += 1;
            }
            //取回区域类型 '91'.length = 2            
            return pduReader.NextString(2 + length);
        }
        /// <summary>
        /// 解析协议
        /// </summary>
        /// <param name="pduReader"></param>
        /// <returns></returns>
        private static string ParseProtocol(PDUReader pduReader)
        {
            return pduReader.NextString(2);
        }
        /// <summary>
        /// 解析数据编码
        /// </summary>
        /// <param name="pduReader"></param>
        /// <returns></returns>
        private static string ParseCharsetEncoding(PDUReader pduReader)
        {
            return pduReader.NextString(2);
        }
        /// <summary>
        /// 解析时间
        /// </summary>
        /// <param name="pduReader"></param>
        /// <returns></returns>
        private static string ParseCenterTime(PDUReader pduReader)
        {
            //14 位长日期格式
            return pduReader.NextString(14);
        }
        /// <summary>
        /// 解析用户数据
        /// </summary>
        /// <param name="pduReader"></param>
        /// <returns></returns>
        private static string ParseUserData(PDUReader pduReader)
        {
            //获得信息内容长度
            int length = Int32.Parse(pduReader.NextString(2), NumberStyles.HexNumber);
            string userData = pduReader.NextString(length * 2);
            return userData;
        }
    }
    #endregion
}
